`timescale 1ns/1ps
`default_nettype none

//`define DEBUG

/* NOTE:
* - 帧率同步模块
*/

module fps_sync
#(parameter
    MAX_PHASE_ERROR = 2000
) (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_rst_n,
    // config
    input  wire         I_cfg_output_enable, // 输出使能
    input  wire         I_cfg_fps_sync_en, // 帧率同步使能
    input  wire [15:0]  I_cfg_chain_cycle, // 串移时钟数
    input  wire [15:0]  I_cfg_min_chain,   // 最小串移周期数
    input  wire [11:0]  I_cfg_chain_num,   // 显示周期串移数
    // frame
    input  wire         I_frame_sync,
    // display control
    output wire         O_display_reset,       // 强制重新开始串移
    input  wire         I_display_ready,       // 输出模块ready
    input  wire         I_display_end,         // 每个显示周期结束标识
    input  wire         I_display_chain_end,   // 每个串移周期结束标识
    output wire         O_display_param_en,    // 更新参数
    output wire [15:0]  O_display_chain_cycle, // 串移时钟数
    output wire [15:0]  O_display_extra_cycle  // 额外的串移时钟数
);
//------------------------Parameter----------------------
// fsm
localparam [2:0]
    IDLE  = 0,
    FREE  = 1,
    PREP  = 2,
    CALC  = 3,
    RESET = 4,
    SYNC  = 5;

localparam
    INIT_PHASE_DIFF = MAX_PHASE_ERROR,
    MAX_PHASE_DIFF  = MAX_PHASE_ERROR * 2;

//------------------------Local signal-------------------
// fsm
reg  [2:0]  state;
reg  [2:0]  next;
reg         last_sync_en;

// prep
reg  [15:0] clock_cnt;
reg         clock_cnt_loop;
reg  [11:0] chain_cnt;
reg         chain_cnt_loop;
reg  [13:0] chain_num;

// calc
reg  [13:0] tmp_chain_max;
reg  [15:0] tmp_extra_cycle;
reg  [15:0] tmp_chain_cycle;

// sync
reg  [13:0] chain_per_frame;
reg         sync_lost;
reg  [12:0] phase_cnt;
reg         phase_detect;
reg         phase_error;
reg         exceed_min_chain;

// display control
reg         param_en;
reg  [15:0] chain_cycle;
reg  [15:0] extra_cycle;

// debug
reg  [6:0]  tick_cnt;
reg  [15:0] us_cnt;

//------------------------Instantiation------------------

//------------------------Body---------------------------
//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
// state
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        state <= IDLE;
    else if (~I_cfg_output_enable)
        state <= IDLE;
    else if (last_sync_en & ~I_cfg_fps_sync_en)
        state <= IDLE;
    else if (!I_display_ready)
        state <= IDLE;
    else
        state <= next;
end

// next
always @(*) begin
    case (state)
        IDLE: begin
            next = FREE;
        end

        FREE: begin
            if (I_frame_sync && I_cfg_fps_sync_en)
                next = PREP;
            else
                next = FREE;
        end

        PREP: begin
            if (I_frame_sync)
                next = CALC;
            else
                next = PREP;
        end

        CALC: begin
            if (~I_frame_sync)
                next = CALC;
            else if (tmp_chain_cycle < I_cfg_min_chain)
                next = FREE;
            else
                next = RESET;
        end

        RESET: begin
            if (phase_cnt == INIT_PHASE_DIFF - 2)
                next = SYNC;
            else
                next = RESET;
        end

        SYNC: begin
            if (sync_lost)
                next = IDLE;
            else if (phase_error)
                next = IDLE;
            else if (exceed_min_chain)
                next = IDLE;
            else
                next = SYNC;
        end

        default: begin
            next = IDLE;
        end
    endcase
end

// last_sync_en
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        last_sync_en <= 1'b0;
    else
        last_sync_en <= I_cfg_fps_sync_en;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++prep+++++++++++++++++++++++++++
// NOTE: 在PREP状态下计算出两帧之间的显示周期数/总串移数

// clock_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        clock_cnt <= 1'b1;
    else if (state == FREE)
        clock_cnt <= 1'b1;
    else if (state == PREP) begin
        if (clock_cnt_loop)
            clock_cnt <= 1'b1;
        else
            clock_cnt <= clock_cnt + 1'b1;
    end
end

// clock_cnt_loop
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        clock_cnt_loop <= 1'b0;
    else if (state == PREP && clock_cnt == I_cfg_chain_cycle - 1'b1)
        clock_cnt_loop <= 1'b1;
    else
        clock_cnt_loop <= 1'b0;
end

// chain_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        chain_cnt <= 1'b1;
    else if (state == FREE)
        chain_cnt <= 1'b1;
    else if (state == PREP && clock_cnt_loop) begin
        if (chain_cnt_loop)
            chain_cnt <= 1'b1;
        else
            chain_cnt <= chain_cnt + 1'b1;
    end
end

// chain_cnt_loop
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        chain_cnt_loop <= 1'b0;
    else if (state == FREE)
        chain_cnt_loop <= 1'b0;
    else if (state == PREP && clock_cnt_loop) begin
        if (chain_cnt == I_cfg_chain_num - 1'b1)
            chain_cnt_loop <= 1'b1;
        else
            chain_cnt_loop <= 1'b0;
    end
end

// chain_num
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        chain_num <= 1'b0;
    else if (state == FREE)
        chain_num <= 1'b0;
    else if (state == PREP) begin
        if (I_frame_sync && chain_num == 1'b0)
            chain_num <= I_cfg_chain_num;
        else if (clock_cnt_loop && chain_cnt_loop)
            chain_num <= chain_num + I_cfg_chain_num;
    end
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++calc+++++++++++++++++++++++++++
// tmp_chain_max
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        tmp_chain_max <= 1'b0;
    else if (state == PREP && I_frame_sync)
        tmp_chain_max <= chain_num - 1'b1;
end

// tmp_extra_cycle
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        tmp_extra_cycle <= 1'b0;
    else if (I_frame_sync)
        tmp_extra_cycle <= 1'b1;
    else if (tmp_extra_cycle == tmp_chain_max)
        tmp_extra_cycle <= 1'b0;
    else
        tmp_extra_cycle <= tmp_extra_cycle + 1'b1;
end

// tmp_chain_cycle
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        tmp_chain_cycle <= 1'b0;
    else if (I_frame_sync)
        tmp_chain_cycle <= 1'b0;
    else if (tmp_extra_cycle == tmp_chain_max)
        tmp_chain_cycle <= tmp_chain_cycle + 1'b1;
end

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++sync+++++++++++++++++++++++++++
// chain_per_frame
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        chain_per_frame <= 1'b0;
    else if (state == CALC)
        chain_per_frame <= 1'b0;
    else if (state == SYNC) begin
        if (I_frame_sync)
            chain_per_frame <= 1'b0;
        else if (I_display_chain_end)
            chain_per_frame <= chain_per_frame + 1'b1;
    end
end

// sync_lost
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        sync_lost <= 1'b0;
    else if (chain_per_frame == 14'h3fff)
        sync_lost <= 1'b1;
`ifdef DEBUG
    else if (us_cnt == 16'hffff)
        sync_lost <= 1'b1;
`endif
    else
        sync_lost <= 1'b0;
end

// phase_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        phase_cnt <= 1'b1;
    else if (I_frame_sync)
        phase_cnt <= 1'b1;
    else if (state == SYNC && I_display_end)
        phase_cnt <= 1'b1;
    else
        phase_cnt <= phase_cnt + 1'b1;
end

// phase_detect
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        phase_detect <= 1'b0;
    else if (state == RESET)
        phase_detect <= 1'b0;
    else if (state == SYNC) begin
        if (I_frame_sync)
            phase_detect <= 1'b1;
        else if (I_display_end)
            phase_detect <= 1'b0;
    end
end

// phase_error
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        phase_error <= 1'b0;
    else if (phase_detect && phase_cnt > MAX_PHASE_DIFF)
        phase_error <= 1'b1;
    else
        phase_error <= 1'b0;
end

// exceed_min_chain
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        exceed_min_chain <= 1'b0;
    else if (I_frame_sync && tmp_chain_cycle < I_cfg_min_chain)
        exceed_min_chain <= 1'b1;
    else
        exceed_min_chain <= 1'b0;
end

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++display control++++++++++++++++
//assign O_display_reset       = (state == IDLE || state == RESET);
assign O_display_reset      = (state == RESET);
assign O_display_param_en    = param_en;
assign O_display_chain_cycle = chain_cycle;
assign O_display_extra_cycle = extra_cycle;

// param_en
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        param_en <= 1'b0;
    else if (state == SYNC && I_frame_sync)
        param_en <= 1'b1;
    else if (I_display_end)
        param_en <= 1'b0;
end

// chain_cycle
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        chain_cycle <= 1'b0;
    else if (state == IDLE)
        chain_cycle <= I_cfg_chain_cycle;
    else if (state == CALC && I_frame_sync)
        chain_cycle <= tmp_chain_cycle;
    else if (state == SYNC && I_frame_sync)
        chain_cycle <= tmp_chain_cycle;
end

// extra_cycle
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        extra_cycle <= 1'b0;
    else if (state == IDLE)
        extra_cycle <= 1'b0;
    else if (state == CALC && I_frame_sync)
        extra_cycle <= tmp_extra_cycle;
    else if (state == SYNC && I_frame_sync)
        extra_cycle <= tmp_extra_cycle;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//++++++++++++++++++++++++debug++++++++++++++++++++++++++
// tick_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        tick_cnt <= 1'b0;
    else if (I_frame_sync)
        tick_cnt <= 1'b0;
    else if (tick_cnt == 7'd124)
        tick_cnt <= 1'b0;
    else
        tick_cnt <= tick_cnt + 1'b1;
end

// us_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        us_cnt <= 1'b0;
    else if (I_frame_sync)
        us_cnt <= 1'b0;
    else if (tick_cnt == 7'd124)
        us_cnt <= us_cnt + 1'b1;
end
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
